﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using Expression_Test.ExpressionGeneric;

namespace Expression_Test.ExpressionGeneric
{
    public class ExpressionGenericMapper<In, Out>
    {
        /// <summary>
        /// 解决并发
        /// </summary>
        private static readonly Func<In, Out> mapper = Init();

        public static Out Trans(In t)
        {
            return mapper(t);
        }
        /// <summary>
        /// 初始化
        /// </summary>
        /// <returns></returns>
        private static Func<In, Out> Init()
        {
            //表达式目录树参数
            ParameterExpression parameterExpression = Expression.Parameter(typeof(In), "m");

            //赋值的集合成员
            List<MemberBinding> memberBindings = new List<MemberBinding>();

            //设置属性映射关系
            SetProperties(parameterExpression, memberBindings);
            //设置字段映射关系
            SetFields(parameterExpression, memberBindings);

            MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(Out)), memberBindings.ToArray());
            Expression<Func<In, Out>> lambda = Expression.Lambda<Func<In, Out>>(memberInitExpression, new ParameterExpression[]
            {
                    parameterExpression
            });

            return lambda.Compile();//拼装是一次性的
        }

        /// <summary>
        /// 设置属性的映射关系
        /// </summary>
        private static void SetProperties(ParameterExpression parameterExpression, List<MemberBinding> memberBindings)
        {
            var inType = typeof(In);
            if (inType == null)
            {
                throw new ArgumentNullException(nameof(inType));
            }

            //设置属性赋值成员
            foreach (var outProp in typeof(Out).GetProperties())
            {
                //除了string类型以外的引用类型 不进行赋值
                if (outProp.PropertyType.IsClass && outProp.PropertyType != typeof(string))
                {
                    continue;
                }
                //In 中属性名是否包含 outProp 当前属性名
                if (!inType.GetProperties().Select(s => s.Name).ToList().Contains(outProp.GetMappingName()))
                {
                    continue;
                }

                //获取In 对象中属性 PropertInfo 对象
                var inProp = inType.GetProperty(outProp.GetMappingName());

                if (inProp == null)
                {
                    continue;
                }

                MemberExpression property = Expression.Property(parameterExpression, inProp);
                MemberBinding memberBinding = Expression.Bind(outProp, property);
                memberBindings.Add(memberBinding);

            }
        }
        /// <summary>
        /// 设置字段的映射关系
        /// </summary>
        private static void SetFields(ParameterExpression parameterExpression, List<MemberBinding> memberBindings)
        {
            var inType = typeof(In);
            if (inType == null)
            {
                throw new ArgumentNullException(nameof(inType));
            }

            //设置属性赋值成员
            foreach (var outProp in typeof(Out).GetFields())
            {
                //除了string类型以外的引用类型 不进行赋值
                if (outProp.FieldType.IsClass && outProp.FieldType != typeof(string))
                {
                    continue;
                }
                //In 中属性名是否包含 outProp 当前属性名
                if (inType.GetProperties().Select(s => s.Name).ToList().Contains(outProp.GetMappingName()))
                {
                    continue;
                }

                //获取In 对象中属性 PropertInfo 对象
                var inProp = inType.GetProperty(outProp.GetMappingName());

                if (inProp == null)
                {
                    continue;
                }

                MemberExpression property = Expression.Property(parameterExpression, inProp);
                MemberBinding memberBinding = Expression.Bind(outProp, property);
                memberBindings.Add(memberBinding);

            }
        }

    }
}
